package com.mybatis.utils;

import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.FileTemplateUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.*;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.Processor;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomUtil;
import com.mybatis.dom.model.Package;
import com.mybatis.dom.model.*;
import org.apache.commons.collections.CollectionUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.*;
import java.util.stream.Collectors;

public class MapperUtils {


    public static Optional<IdDomElement> findParentIdDomElement(@Nullable PsiElement element) {
        DomElement domElement = DomUtil.getDomElement(element);
        if (null == domElement) {
            return Optional.empty();
        }
        if (domElement instanceof IdDomElement) {
            return Optional.of((IdDomElement) domElement);
        }
        return Optional.ofNullable(DomUtil.getParentOfType(domElement, IdDomElement.class, true));
    }


    public static PsiElement createMapperFromFileTemplate(@NotNull String fileTemplateName, @NotNull String fileName, @NotNull PsiDirectory directory, @Nullable Properties pops, @NotNull Project project) throws Exception {
        FileTemplate fileTemplate = FileTemplateManager.getInstance(project).getJ2eeTemplate(fileTemplateName);
        PsiFile file = directory.findFile(fileName + "." + fileTemplate.getExtension());

        if (file != null) {
            Messages.showMessageDialog("file " + file.getName() + " already exists", "Generate File", Messages.getWarningIcon());
            return null;
        }
        return FileTemplateUtil.createFromTemplate(fileTemplate, fileName, pops, directory);
    }


    @NotNull
    public static Collection<PsiDirectory> findMapperDirectories(@NotNull Project project) {
        return Collections2.transform(findMappers(project), input -> {
                    if (input != null) {
                        XmlElement xmlElement = input.getXmlElement();
                        if (xmlElement == null) {
                            return null;
                        }
                        return xmlElement.getContainingFile().getContainingDirectory();
                    } else {
                        return null;
                    }
                }
        );
    }


    public static boolean isElementWithinMybatisFile(@NotNull PsiElement element) {
        PsiFile psiFile = element.getContainingFile();
        return (element instanceof XmlElement && DomUtils.isMybatisFile(psiFile));
    }


    @NotNull
    @NonNls
    public static Collection<Mapper> findMappers(@NotNull Project project) {
        return DomUtils.findDomElements(project, Mapper.class);
    }


    @NotNull
    @NonNls
    public static Collection<Mapper> findMappers(@NotNull Project project, @NotNull String namespace) {
        List<Mapper> result = Lists.newArrayList();
        for (Mapper mapper : findMappers(project)) {
            if (getNamespace(mapper).equals(namespace)) {
                result.add(mapper);
            }
        }
        return result;
    }


    @NotNull
    public static Collection<Mapper> findMappers(@NotNull Project project, @NotNull PsiClass clazz) {
        return JavaUtils.isElementWithinInterface(clazz) ? findMappers(project, Objects.requireNonNull(clazz.getQualifiedName())) : Collections.emptyList();
    }


    @NotNull
    public static Collection<Mapper> findMappers(@NotNull Project project, @NotNull PsiMethod method) {
        PsiClass clazz = method.getContainingClass();
        return (null == clazz) ? Collections.emptyList() : findMappers(project, clazz);
    }


    @NonNls
    public static Optional<Mapper> findFirstMapper(@NotNull Project project, @NotNull String namespace) {
        Collection<Mapper> mappers = findMappers(project, namespace);
        return CollectionUtils.isEmpty(mappers) ? Optional.empty() : Optional.of(mappers.iterator().next());
    }


    @NonNls
    public static Optional<Mapper> findFirstMapper(@NotNull Project project, @NotNull PsiClass clazz) {
        String qualifiedName = clazz.getQualifiedName();
        return (null != qualifiedName) ? findFirstMapper(project, qualifiedName) : Optional.empty();
    }


    @NonNls
    public static Optional<Mapper> findFirstMapper(@NotNull Project project, @NotNull PsiMethod method) {
        PsiClass containingClass = method.getContainingClass();
        return (null != containingClass) ? findFirstMapper(project, containingClass) : Optional.empty();
    }


    @NotNull
    @NonNls
    public static Mapper getMapper(@NotNull DomElement element) {
        Optional<Mapper> optional = Optional.ofNullable(DomUtil.getParentOfType(element, Mapper.class, true));
        if (optional.isPresent()) {
            return optional.get();
        }
        throw new IllegalArgumentException("Unknown element");
    }


    @NotNull
    @NonNls
    public static String getNamespace(@NotNull Mapper mapper) {
        String ns = mapper.getNamespace().getStringValue();
        return (null == ns) ? "" : ns;
    }


    @NotNull
    @NonNls
    public static String getNamespace(@NotNull DomElement element) {
        return getNamespace(getMapper(element));
    }


    @NonNls
    public static boolean isMapperWithSameNamespace(@Nullable Mapper mapper, @Nullable Mapper target) {
        return (null != mapper && null != target && getNamespace(mapper).equals(getNamespace(target)));
    }


    @Nullable
    @NonNls
    public static <T extends IdDomElement> String getId(@NotNull T domElement) {
        return domElement.getId().getRawText();
    }


    @NotNull
    @NonNls
    public static <T extends IdDomElement> String getIdSignature(@NotNull T domElement) {
        return getNamespace(domElement) + "." + getId(domElement);
    }


    @NotNull
    @NonNls
    public static <T extends IdDomElement> String getIdSignature(@NotNull T domElement, @NotNull Mapper mapper) {
        Mapper contextMapper = getMapper(domElement);
        String id = getId(domElement);
        if (id == null) {
            id = "";
        }
        String idsignature = getIdSignature(domElement);
        return isMapperWithSameNamespace(contextMapper, mapper) ? id : idsignature;
    }


    public static void processConfiguredTypeAliases(@NotNull Project project, @NotNull Processor<TypeAlias> processor) {
        for (Configuration conf : getMybatisConfigurations(project)) {
            for (TypeAliases tas : conf.getTypeAliases()) {
                for (TypeAlias ta : tas.getTypeAlias()) {
                    String stringValue = ta.getAlias().getStringValue();
                    if (null != stringValue && !processor.process(ta)) {
                        return;
                    }
                }
            }
        }
    }


    private static Collection<Configuration> getMybatisConfigurations(Project project) {
        return DomUtils.findDomElements(project, Configuration.class);
    }


    public static void processConfiguredPackage(@NotNull Project project, @NotNull Processor<Package> processor) {
        for (Configuration conf : getMybatisConfigurations(project)) {
            for (TypeAliases tas : conf.getTypeAliases()) {
                for (Package pkg : tas.getPackages()) {
                    if (!processor.process(pkg))
                        return;
                }
            }
        }
    }

    public static String getTableName(@NotNull Project project, @NotNull PsiClass psiClass) {
        Collection<Mapper> mappers = findMappers(project, psiClass);
        if (mappers.isEmpty()) {
            return "";
        }
        Mapper mapper = mappers.stream().findFirst().get();
        List<Sql> sqls = mapper.getSqls();
        List<Insert> inserts = mapper.getInserts();
        if (!inserts.isEmpty()) {
            for (Insert insert : inserts) {
                List<Include> includeList = insert.getIncludes();
                XmlTag xmlTag = insert.getXmlTag();
                if (xmlTag == null) {
                    continue;
                }
                String text = xmlTag.getText();
                text = text.substring(text.indexOf("insert"));
                text = text.substring(text.indexOf("into") + 4);
                if (text.contains("prefix=\"(\"")) {
                    text = text.substring(0, text.indexOf("<trim")).trim();
                } else {
                    text = text.substring(0, text.indexOf("(")).trim();
                }
                if (includeList.isEmpty()) {
                    if (StringUtils.isNotBlank(text)) {
                        return text;
                    }
                } else if (includeList.size() == 1) {
                    if (text.contains("<include") && sqls.size() > 0) {
                        XmlAttribute xmlAttribute = includeList.get(0).getRefId().getXmlAttribute();
                        if (xmlAttribute != null) {
                            String id = xmlAttribute.getValue();
                            if (StringUtils.isBlank(id)) {
                                continue;
                            }
                            for (Sql sql : sqls) {
                                XmlAttribute sqlXmlAttribute = sql.getId().getXmlAttribute();
                                if (sqlXmlAttribute == null) {
                                    continue;
                                }
                                if (id.equals(sqlXmlAttribute.getValue())) {
                                    XmlTag sqlXmlTag = sql.getXmlTag();
                                    if (sqlXmlTag == null) {
                                        continue;
                                    }
                                    String sqlText = sqlXmlTag.getValue().getText().trim();
                                    if (StringUtils.isNotBlank(sqlText)) {
                                        return sqlText;
                                    }
                                }

                            }
                        }
                    }

                }
            }

        }
        //进行class 进行反转为table
        String className = mapper.getResultMaps().get(0).getType().getRawText();
        if (StringUtils.isBlank(className)) {
            return "";
        }
        String[] packageNames = className.split("\\.");
        String[] strings = StringUtils.splitByCharacterTypeCamelCase(packageNames[packageNames.length - 1]);
        return Arrays.stream(strings).map(StringUtils::lowerCaseFirstChar).collect(Collectors.joining("_"));
    }
}
